package com.zzyl.nursing.service.impl;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.utils.DateUtils;
import com.zzyl.nursing.domain.AlertData;
import com.zzyl.nursing.domain.DeviceData;
import com.zzyl.nursing.mapper.AlertDataMapper;
import com.zzyl.nursing.mapper.DeviceMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.nursing.service.IFloorService;
import com.zzyl.system.mapper.SysUserRoleMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import com.zzyl.nursing.mapper.AlertRuleMapper;
import com.zzyl.nursing.domain.AlertRule;
import com.zzyl.nursing.service.IAlertRuleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 报警规则Service业务层处理
 * 
 * @author vast
 * @date 2025-01-09
 */
@Slf4j
@Service
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService
{
    @Autowired
    private AlertRuleMapper alertRuleMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private SysUserRoleMapper userRoleMapper;

    @Value("${alert.deviceMaintainerRole}")
    private String deviceMaintainerRole;

    @Value("${alert.managerRole}")
    private String managerRole;

    @Autowired
    private IAlertDataService alertDataService;

    /**
     * 查询报警规则
     * 
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id)
    {
        return getById(id);
    }

    /**
     * 查询报警规则列表
     * 
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule)
    {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule)
    {
        return save(alertRule) ? 1 : 0;
    }

    /**
     * 修改报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule)
    {
        return updateById(alertRule) ? 1 : 0;
    }

    /**
     * 批量删除报警规则
     * 
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids)
    {
        return removeByIds(Arrays.asList(ids)) ? 1 : 0;
    }

    /**
     * 删除报警规则信息
     * 
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id)
    {
        return removeById(id) ? 1 : 0;
    }

    /**
     * 数据报警过滤
     */
    @Override
    public void alertFilter() {
        // 1.查询有没有有效的报警规则 查询条件是报警规则表的status=1
        Long count = lambdaQuery().eq(AlertRule::getStatus, 1).count();
        if (count <= 0){
            log.info("没有有效的报警规则！");
            return;
        }
        // 2.查询所有上报数据
        List<Object> allDataList = redisTemplate.opsForHash().values(CacheConstants.IOT_PRODUCT_LIST);
        if (CollUtil.isEmpty(allDataList)){
            log.info("设备上报数据为空！");
            return;
        }

        // 3.解析上报数据 并添加到一个新的集合中
        List<DeviceData> deviceDataList = new ArrayList<>();
        allDataList.forEach(dataList -> {
            deviceDataList.addAll(JSONUtil.toList((String) dataList,DeviceData.class));
        });

        // 4.将上报数据交给数据报警处理器处理
        deviceDataList.forEach(this::alertFilter);
    }

    /**
     * 获取所有的规则
     * @param deviceData
     */
    private void alertFilter(DeviceData deviceData) {
        List<AlertRule> allRulesList = new ArrayList<>();

        // 一个产品下所有设备的报警规则
        List<AlertRule> productRulesList = getRulesByProductKey(deviceData.getProductKey(),deviceData.getFunctionId());

        // 产品下某一个设备的报警规则
        List<AlertRule> deviceRulesList = getRulesByIotId(deviceData.getIotId(),deviceData.getFunctionId());

        // 合并所有报警规则
        if (CollUtil.isNotEmpty(productRulesList)) {
            allRulesList.addAll(productRulesList);
        }
        if (CollUtil.isNotEmpty(deviceRulesList)){
            allRulesList.addAll(deviceRulesList);
        }

        // 如果报警规则为空 则退出
        if (CollUtil.isEmpty(allRulesList)){
            return;
        }
        // 不为空 则进行报警规则过滤校验
        allRulesList.forEach(alertRule -> deviceDataAlarmHandler(alertRule,deviceData));
    }

    /**
     * 根据报警规则 过滤报警数据
     * @param alertRule
     * @param deviceData
     */
    private void deviceDataAlarmHandler(AlertRule alertRule, DeviceData deviceData) {
        // 1.进行第一层校验  校验报警时间是否在有效时间内  00:00:00~23:59:59
        String alertEffectivePeriod = alertRule.getAlertEffectivePeriod();
        // 分割有效时间 拿到开始时间和结束时间
        String[] split = alertEffectivePeriod.split("~");
        LocalTime start = LocalTime.parse(split[0]);
        LocalTime end  = LocalTime.parse(split[1]);
        // 判断报警时间
        LocalTime alarmTime = deviceData.getAlarmTime().toLocalTime();
        // 在开始时间之前不上报
        if(alarmTime.isBefore(start)){
            return;
        }
        // 在结束时间之后不上报
        if (alarmTime.isAfter(end)){
            return;
        }

        // 2.进行第二层校验 校验是否在阈值内
        String iotId = deviceData.getIotId();
        // 统计报警次数的key
        String aggCountKey = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + iotId + ":" + deviceData.getFunctionId() + ":" + alertRule.getId();
        // 阈值
        Double value = alertRule.getValue();
        // 上报数据值
        String dataValue = deviceData.getDataValue();
        // 报警规则操作符
        String operator = alertRule.getOperator();
        // 比较阈值与实际值  -1实际值小于阈值  0实际值等于阈值  1实际值大于阈值
        int compare = NumberUtil.compare(Double.valueOf(dataValue), value);
        // 判断是否报警 如果实际值<阈值并且比较结果是>=0   如果实际值>阈值并且比较结果是<0  不报警
        if ("<".equals(operator) && compare>=0 || ">=".equals(operator) && compare < 0){
            // 删除redis的报警次数
            log.info("上报的数据不符合异常数据！");
            redisTemplate.delete(aggCountKey);
            return;
        }

        // 3.进行第三层校验 校验是否在沉默周期
        // 沉默周期
        Integer alertSilentPeriod = alertRule.getAlertSilentPeriod();
        // 沉默周期的key不存在  沉默周期已结束
        String silentKey = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + iotId + ":" + deviceData.getFunctionId() + ":" + alertRule.getId();
        // 如果key存在 则在沉默周期 退出
        if (redisTemplate.hasKey(silentKey)){
            return;
        }

        // 4.进行第四层校验 校验是否在持续周期
        // 如果key不存在且满足持续周期 报警次数+1
        Long increment = redisTemplate.opsForValue().increment(aggCountKey);
        // 判断是否满足持续周期 报警次数>=持续周期则满足
        Integer duration = alertRule.getDuration();
        if (NumberUtil.compare(increment,duration) < 0){
            // 没达到持续周期 退出
            return;
        }
        // 达到持续周期 删除报警次数
        redisTemplate.delete(aggCountKey);
        // 设置沉默周期 并指定key的有效时间为沉默周期的时间
        redisTemplate.opsForValue().set(silentKey,"1",alertSilentPeriod, TimeUnit.MINUTES);

        // 5.查找需要通知的人
        Set<Long> userIds = new HashSet<>();
        if (ObjectUtil.equal(alertRule.getAlertDataType(),0)){
            // 老人数据
            if (ObjectUtil.equal(deviceData.getLocationType(),0)){
                // 随身设备  通知护理人员
                userIds.addAll(deviceMapper.selectNursingIdsByIotIdWithElder(iotId));
            }else {
                // 床位设备  通知护理人员
                userIds.addAll(deviceMapper.selectNursingIdsByIotIdWithBed(iotId));
            }
        }else {
            // 设备数据  通知维修工
            userIds.addAll(userRoleMapper.selectUserIdByRoleName(deviceMaintainerRole));
        }

        // 所有报警都要通知超级管理员
        userIds.addAll(userRoleMapper.selectUserIdByRoleName(managerRole));

        // 6.批量保存数据
        insertAlertData(userIds, alertRule, deviceData);
    }

    /**
     * 保存报警数据
     * @param allUserIds
     * @param rule
     * @param deviceData
     */
    private void insertAlertData(Collection<Long> allUserIds, AlertRule rule, DeviceData deviceData) {
        // 创建一个所有人的报警模板
        AlertData alertData = BeanUtil.toBean(deviceData, AlertData.class);
        alertData.setAlertRuleId(rule.getId());
        // 心率<60,持续3个周期就报警
        String alertReason = StrUtil.format("{}{}{},持续{}个周期就报警", rule.getFunctionName(), rule.getOperator(), rule.getValue(), rule.getDuration());
        alertData.setAlertReason(alertReason);
        alertData.setStatus(0);
        alertData.setType(rule.getAlertDataType());

        // 遍历allUserIds
        List<AlertData> list = allUserIds.stream().map(userId -> {
            AlertData dbAlertData = BeanUtil.toBean(alertData, AlertData.class);
            dbAlertData.setUserId(userId);
            // 清空数据自带的id
            dbAlertData.setId(null);
            return dbAlertData;
        }).collect(Collectors.toList());

        // 批量保存
        alertDataService.saveBatch(list);
    }

    /**
     * 产品下一个设备的报警规则
     * @param iotId
     * @param functionId
     * @return
     */
    private List<AlertRule> getRulesByIotId(String iotId, String functionId) {
        return lambdaQuery().eq(AlertRule::getIotId,iotId)
                .eq(AlertRule::getFunctionId,functionId).list();
    }

    /**
     * 产品下所有设备的报警规则
     * @param productKey
     * @param functionId
     * @return
     */
    private List<AlertRule> getRulesByProductKey(String productKey, String functionId) {
        return lambdaQuery().eq(AlertRule::getProductKey,productKey)
                .eq(AlertRule::getFunctionId,functionId)
                .eq(AlertRule::getIotId,"-1").list();
    }
}
